{*******************************************************************************
* The contents of this file are used with permission, subject to the Mozilla   *
* Public License Version 1.1 (the "License"); you may not use this file except *
* in compliance with the License. You may obtain a copy of the License at      *
* http://www.mozilla.org/MPL/                                                  *
*                                                                              *
* Software distributed under the License is distributed on an "AS IS" basis,   *
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for *
* the specific language governing rights and limitations under the License.    *
*                                                                              *
* The Original Code is distributed as part of the "mediate" product and is     *
* Copyright (C) @home media limited. All Rights Reserved.                      *
*                                                                              *
* Email: support@athomemedia.co.uk                                             *
* Web:   http://www.athomemedia.co.uk                                          *
*******************************************************************************}

unit MediaFiles;
{$WARN SYMBOL_PLATFORM OFF}

interface

uses Classes, MetaData, Playlist, AHMTypes, MediaFolders, TagParser,
     MediaSchema, VirtualPaths;

type
  TAHMMediaFilesManager = class
  private
    FTxGenres: String;
    FTxArtists: String;
    FTxAlbums: String;
    FTxTracks: String;
    FTxFolders: String;
    FTxAllGenres: String;
    FTxAllArtists: String;
    FTxAllAlbums: String;
    FTxAllStations: String;
    FSchema: TAHMMediaSchema;
    FPath: TAHMVirtualPathManager;
    FTagParser: TAHMMediaTagParser;
  protected
    function ClearMediaList(Results: TAHMMediaList): Boolean;
    procedure ListMetaFolder(Results: TAHMMediaList);
    procedure ListVirtualFolder(MediaFolder: TAHMMediaFolder; VirtualRoot, Folder: WideString; Types: TAHMMediaTypes; Results: TAHMMediaList);
    procedure ListMedia(VirtualRoot, Root, Folder: WideString; Types: TAHMMediaTypes; Results: TAHMMediaList); overload;
    procedure ListMedia(VirtualRoot, Root, Folder, Mask: WideString; Types: TAHMMediaTypes; Results: TAHMMediaList); overload;
    procedure AddListParent(Root, Folder: WideString; Results: TAHMMediaList);
  public
    constructor Create;
    destructor Destroy; override;
    function BrowseImages(Folder: WideString; Results: TAHMMediaList): Boolean;
    function BrowseVideos(Folder: WideString; Results: TAHMMediaList): Boolean;
    function BrowseMusic(var Folder: WideString; Results: TAHMMediaList): Boolean;
    function BrowseRadio(Folder: WideString; Results: TAHMMediaList): Boolean;
    function BrowseMedia(Folder: WideString; Results: TAHMMediaList): Boolean;
//    function ListMediaFolder(Folder: WideString; Results: TAHMMediaList): Boolean;
//    function ListMediaItem(URL: WideString; Results: TAHMMediaList): Boolean;
  end;

implementation

uses Windows, SysUtils, StrUtils, Math, Unicode, Logging, Utilities,
     ApplicationManager, DBSchema;

//----------------------------------------------------------------------------//

function TweakMediaType(SearchRec: TSearchRecW): WideString;
begin
  // Add trailing slash to regular folders
  if (SearchRec.Attr and faDirectory) <> 0 then
  begin
    if SearchRec.Name = '.' then Result := ''
    else if SearchRec.Name = '..' then Result := '..'
    else Result := SearchRec.Name + PathDelim;
  end
  else
    Result := SearchRec.Name;
end;

//----------------------------------------------------------------------------//

constructor TAHMMediaFilesManager.Create;
begin
  inherited;

  FPath := TAHMVirtualPathManager.Create;

  // Configure path for thumbnail cache
  FTagParser := TAHMMediaTagParser.Create;
  FTagParser.ThumbnailPath := ApplicationManagerInstance.ThumbnailPath;
  FTagParser.CreateThumbnails := True;

  // Configure path for media database (this opens it)
  FSchema := TAHMMediaSchema.Create;
  FSchema.FileName := ApplicationManagerInstance.ConfigPath + MEDIA_DB_FILENAME;
  FSchema.CheckSchema;

  // Set default translations for virtual media path names
//TODO: need to lookup theme specific replacements for these in LanguageManager
  FTxGenres := 'Genres\';
  FTxArtists := 'Artists\';
  FTxAlbums := 'Albums\';
  FTxTracks := 'Tracks\';
  FTxFolders := 'Folders\';
  FTxAllGenres := 'All Genres\';
  FTxAllArtists := 'All Artists\';
  FTxAllAlbums := 'All Albums\';
  FTxAllStations := 'All Stations\';
end;

destructor TAHMMediaFilesManager.Destroy;
begin
  FPath.Free;
  FSchema.Free;
  FTagParser.Free;

  inherited;
end;

function TAHMMediaFilesManager.ClearMediaList(Results: TAHMMediaList): Boolean;
begin
  Result := False;

  if Assigned(Results) then
  begin
    Results.Clear;
    Result := True;
  end;
end;

procedure TAHMMediaFilesManager.ListMetaFolder(Results: TAHMMediaList);
var
  Parent, Folder, ImageURL: WideString;
  Rows: TAHMDBResult;
  Compilations: Boolean;
  MediaItem, TotalItem: TAHMMediaItem;
  Album: PAlbumInfo;
  Artist: PArtistInfo;
  Genre: PGenreInfo;
  URL: WideString;
begin
  Rows := nil;
  Parent := FPath.Parent;
  Folder := FPath.FolderIndex;

  case FPath.VirtualPath of
    vmpRoot:
    begin
      // Build a list of supported virtual paths
      Results.AddItem('1', FTxGenres, mtFolder, '', 'Genres\', '', '');
      Results.AddItem('2', FTxArtists, mtFolder, '', 'Artists\', '', '');
      Results.AddItem('3', FTxAlbums, mtFolder, '', 'Albums\', '', '');
      Results.AddItem('4', FTxTracks, mtFolder, '', 'Tracks\', '', '');
      Results.AddItem('5', FTxFolders, mtFolder, '', 'Folders\', '', '');
    end;
    vmpGenres:
    begin
      // Build a list of genres
//TODO: parent folder names should be translated into other languages, but only
// for display purposes, otherwise we confuse the virtual path browser
      Results.AddItem(Parent, FPath.ParentCaption, mtParent, Folder, '..', '', '');
      TotalItem := Results.AddItem('', FTxAllGenres, mtFolder, '', 'Genres\Artists\', '', '');
      Rows := FSchema.ListGenres;
      with Rows do while not EOF do
      begin
        MediaItem := Results.AddItem(FieldAsString(FIELD_NAME),
                                     FieldAsString(FIELD_NAME), mtFolder, '',
                                     'Genre.' + FieldAsString(FIELD_ID) + '\Artists\',
                                     '', '');
        MediaItem.Duration := FieldAsInteger(FIELD_SIZE);
        TotalItem.Duration := TotalItem.Duration + MediaItem.Duration;
        Next;
      end;
    end;
    vmpArtists:
    begin
      // Build a list of all artists or artists for specified genre
      Results.AddItem(Parent, FPath.ParentCaption, mtParent, Folder, '..', '', '');
      TotalItem := Results.AddItem('', FTxAllArtists, mtFolder, '', 'Artists\Albums\', '', '');
      Compilations := ApplicationManagerInstance.PreferencesManager.CompilationArtists;
      Rows := FSchema.ListArtists(FPath.GenreId, Compilations);
      with Rows do while not EOF do
      begin
        ImageURL := DecodeWideString(FieldAsString(FIELD_URL));

        MediaItem := Results.AddItem(FieldAsString(FIELD_NAME),
                                     FieldAsString(FIELD_NAME), mtFolder, '',
                                     'Artist.' + FieldAsString(FIELD_ID) + '\Albums\',
                                     '', ImageURL);
        MediaItem.Duration := FieldAsInteger(FIELD_SIZE);
        TotalItem.Duration := TotalItem.Duration + MediaItem.Duration;
        Next;
      end;
    end;
    vmpAlbums:
    begin
      // Cache artists, albums and genres locally for speed
      FSchema.BuildCaches;

      // Build a list of albums or albums for specified genre or artist or both
      Results.AddItem(Parent, FPath.ParentCaption, mtParent, Folder, '..', '', '');
      TotalItem := Results.AddItem('', FTxAllAlbums, mtFolder, '', 'Albums\Tracks\', '', '');
      Rows := FSchema.ListAlbums(FPath.GenreId, FPath.ArtistId);
      with Rows do while not EOF do
      begin
        Artist := FSchema.Artists[FieldAsInteger(FIELD_ARTIST)];
        ImageURL := DecodeWideString(FieldAsString(FIELD_URL));

        MediaItem := Results.AddItem(FieldAsString(FIELD_NAME),
                                     FieldAsString(FIELD_NAME), mtFolder, '',
                                     'Album.' + FieldAsString(FIELD_ID) + '\Tracks\',
                                     '', ImageURL);
        MediaItem.Author := Artist.Name;
        MediaItem.Duration := FieldAsInteger(FIELD_SIZE);
        TotalItem.Duration := TotalItem.Duration + MediaItem.Duration;
        Next;
      end;
    end;
    vmpTracks:
    begin
      // Cache artists, albums and genres locally for speed
      FSchema.BuildCaches;

      // Build a list of tracks for specified genre/artist/album combination
      Results.AddItem(Parent, FPath.ParentCaption, mtParent, Folder, '..', '', '');
      Rows := FSchema.ListTracks(FPath.GenreId, FPath.ArtistId, FPath.AlbumId);
      with Rows do while not EOF do
      begin
        Album := FSchema.Albums[FieldAsInteger(FIELD_ALBUM)];
        Artist := FSchema.Artists[FieldAsInteger(FIELD_ARTIST)];
        Genre := FSchema.Genres[FieldAsInteger(FIELD_GENRE)];
        URL := DecodeWideString(FieldAsString(FIELD_URL));

        if FieldAsBoolean(FIELD_IMAGE) then
          ImageURL := FTagParser.ThumbnailURL(URL)
        else
          ImageURL := DecodeWideString(Album.ImageURL);

        MediaItem := Results.AddItem(FieldAsString(FIELD_NAME), FieldAsString(FIELD_NAME),
                                     mtAudio, '', '', URL, ImageURL);
        MediaItem.Author := Artist.Name;
        MediaItem.Group := Album.Name;
        MediaItem.Category := Genre.Name;
        MediaItem.Duration := FieldAsInteger(FIELD_SIZE);
        MediaItem.Sequence := FieldAsInteger(FIELD_TRACK);
        MediaItem.Year := FieldAsInteger(FIELD_YEAR);
        MediaItem.Bitrate := FieldAsInteger(FIELD_BITRATE);
        Next;
      end;

      // Sort tracks alphabetically except when listing album (faster in Delphi!)
      if FPath.AlbumId = 0 then Results.Sort;
    end;
  end;

  Rows.Free;
end;

procedure TAHMMediaFilesManager.ListVirtualFolder(MediaFolder: TAHMMediaFolder; VirtualRoot, Folder: WideString; Types: TAHMMediaTypes; Results: TAHMMediaList);
begin
  // Is this a virtual folder or just a normal directory?
  if MediaFolder.VirtualFolder then
  begin
    // Are we showing root folder to user?
    if Folder = '' then
      // Yes, add a dummy folder here with same name as our virtual folder
      Results.AddUniqueItem(VirtualRoot, MediaFolder.FolderName, mtFolder, '', '', MediaFolder.FolderName, '')
    else
      // No, so show folder content if current folder is a subdirectory of our virtual folder
      if MediaFolder.IsThisFolder(Folder) then
        // Display contents of our virtual folder (minus virtual prefix)
        ListMedia(VirtualRoot, MediaFolder.FolderPath, Folder, Types, Results);
  end
  else
    // This is a regular physical folder, so just list its contents
    ListMedia(VirtualRoot, MediaFolder.FolderPath, Folder, Types, Results);
end;

procedure TAHMMediaFilesManager.ListMedia(VirtualRoot, Root, Folder: WideString; Types: TAHMMediaTypes; Results: TAHMMediaList);
begin
  // Call method below using default (all files) mask
  ListMedia(VirtualRoot, Root, Folder, '*.*', Types, Results);
end;

procedure TAHMMediaFilesManager.ListMedia(VirtualRoot, Root, Folder, Mask: WideString; Types: TAHMMediaTypes; Results: TAHMMediaList);
var
  Found, Attr: Integer;
  SearchRec: TSearchRecW;
  MediaType: TAHMMediaType;
  DisplayName: String;
  AlbumArtURL: WideString;
  FoundMatch, ArtMatch: Integer;
  MediaItem: TAHMMediaItem;
  ErrorMode: Word;
  Tags: TAHMMediaTags;
begin
  // Suppress cheesy 'no disk in drive' windows errors
  ErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
  try
    // Initialise album art match - no matches
    AlbumArtURL := '';
    ArtMatch := -1;

    // Extra pass for audio files to look for album art
    if (mtAll in Types) or (mtAudio in Types) then
    begin
      // Include hidden files created by WMP
      Attr := faAnyFile;
      Found := FindFirstW(Root + Folder + Mask, Attr, SearchRec);
      while Found = 0 do
      begin
        if (GetMediaType(SearchRec.Name) = mtImage) then
        begin
          FoundMatch := GetAlbumArtMatch(SearchRec.Name);
          if FoundMatch > ArtMatch then
          begin
            ArtMatch := FoundMatch;
            AlbumArtURL := Root + Folder + SearchRec.Name;
          end;
        end;
        Found := FindNextW(SearchRec);
      end;
      FindCloseW(SearchRec);
    end;

    // Exclude hidden and system files
    Attr := faDirectory or faArchive or faReadOnly;
    Found := FindFirstW(Root + Folder + Mask, Attr, SearchRec);
    while Found = 0 do
    begin
      // Determine type of found file/folder
      SearchRec.Name := TweakMediaType(SearchRec);
      MediaType := GetMediaType(SearchRec.Name);

      // Filter required media types
      if (mtAll in Types) or (MediaType in Types) then
      begin
        if MediaType = mtFolder then
        begin
          // Only add unique folders
          Results.AddUniqueItem(SearchRec.Name, SearchRec.Name, MediaType, '', SearchRec.Name, '', '');
        end
        else if MediaType = mtAudio then
        begin
          // Parse tags for supported audio formats
          Tags := FTagParser.Parse(Root + Folder, SearchRec.Name);
          if (Tags <> nil) and (Tags.Caption <> '') then DisplayName := Tags.Caption
                                                    else DisplayName := ExtractFileCaptionW(SearchRec.Name);
          MediaItem := Results.AddItem(SearchRec.Name, DisplayName, MediaType, Root, Folder, SearchRec.Name, AlbumArtURL);
          MediaItem.Size := SearchRec.Size;
          if Tags <> nil then
          begin
            MediaItem.Author := Tags.TitleArtist;
            MediaItem.Group := Tags.CompilationTitle;
            MediaItem.Category := Tags.Genre;
            MediaItem.Duration := Tags.Duration;
            MediaItem.Sequence := Tags.TrackNumber;
            MediaItem.Year := Tags.Year;
            MediaItem.Bitrate := Tags.Bitrate;
            if Tags.ImageURL <> '' then
              MediaItem.Icon := Tags.ImageURL;
          end
          else
            MediaItem.Timestamp := FileDateToDateTime(SearchRec.Time);
        end
        else if MediaType = mtVideo then
        begin
          // Parse tags for supported video formats
          Tags := FTagParser.Parse(Root + Folder, SearchRec.Name);
          if (Tags <> nil) and (Tags.Caption <> '') then DisplayName := Tags.Caption
                                                    else DisplayName := ExtractFileCaptionW(SearchRec.Name);
          MediaItem := Results.AddItem(SearchRec.Name, DisplayName, MediaType, Root, Folder, SearchRec.Name, '');
          MediaItem.Size := SearchRec.Size;
          if Tags <> nil then
          begin
            MediaItem.Duration := Tags.Duration;
            MediaItem.Sequence := Tags.TrackNumber;
            MediaItem.Year := Tags.Year;
            MediaItem.Bitrate := Tags.Bitrate;
            if Tags.ImageURL <> '' then
              MediaItem.Icon := Tags.ImageURL;
          end
          else
            MediaItem.Timestamp := FileDateToDateTime(SearchRec.Time);
        end
        else if MediaType = mtDVD then
        begin
          // Lookup volume name for inserted DVD
          DisplayName := GetVolumeName(Root + Folder + SearchRec.Name);
          if DisplayName = '' then DisplayName := 'DVD';
          Results.AddItem(SearchRec.Name, DisplayName, MediaType, Root, Folder, SearchRec.Name, '');
        end
        else if MediaType = mtPlaylist then
        begin
          // Just add playlist name for now
          DisplayName := ExtractFileCaptionW(SearchRec.Name);
          Results.AddItem(SearchRec.Name, DisplayName, MediaType, Root, Folder, SearchRec.Name, '');
        end
        else if MediaType = mtImage then
        begin
          // Add image file details and URL - tags not supported
          DisplayName := ExtractFileCaptionW(SearchRec.Name);
          MediaItem := Results.AddItem(SearchRec.Name, DisplayName, MediaType, Root, Folder, SearchRec.Name, Root + Folder + SearchRec.Name);
          MediaItem.Size := SearchRec.Size;
          MediaItem.Timestamp := FileDateToDateTime(SearchRec.Time);
        end;
      end;

      Found := FindNextW(SearchRec);
    end;
    FindCloseW(SearchRec);
  finally
    SetErrorMode(ErrorMode) ;
  end;
end;

procedure TAHMMediaFilesManager.AddListParent(Root, Folder: WideString; Results: TAHMMediaList);
var
  Parent: WideString;
begin
  // Check that we have a parent option unless this is top level
  if Root + Folder = '' then Exit;
  Parent := ParentFolderW(Root + Folder);

  // Add a default parent option - easier than testing for parent within folders
  if Folder = '' then
    Results.AddItem(Parent, Parent, mtParent, Root, '', '..', '')
  else
    Results.AddItem(Parent, LastFolderW(Parent), mtParent, '', LastFolderW(Folder), '..', '');
end;

function TAHMMediaFilesManager.BrowseImages(Folder: WideString; Results: TAHMMediaList): Boolean;
var
  i: Integer;
  Types: TAHMMediaTypes;
begin
  // Use function above to retrieve media from relevant content folder
  Result := False;
  Types := [mtParent, mtFolder, mtImage, mtVideo];
  if ClearMediaList(Results) then
    with ApplicationManagerInstance.MediaManager.MediaLocations do
    begin
      for i := 0 to Pred(ImageFolders.Count) do
        ListVirtualFolder(ImageFolders[i], '', Folder, Types, Results);
      Result := True;
    end;

  // Add default parent folder item to results & sort
  AddListParent('', Folder, Results);
  Results.Sort;
end;

function TAHMMediaFilesManager.BrowseVideos(Folder: WideString; Results: TAHMMediaList): Boolean;
var
  i: Integer;
  Types: TAHMMediaTypes;
begin
  // Use function above to retrieve media from relevant content folder
  Result := False;
  Types := [mtParent, mtFolder, mtVideo, mtDVD];
  if ClearMediaList(Results) then
    with ApplicationManagerInstance.MediaManager.MediaLocations do
    begin
      for i := 0 to Pred(VideoFolders.Count) do
        ListVirtualFolder(VideoFolders[i], '', Folder, Types, Results);
      Result := True;
    end;

  // Add default parent folder item to results & sort
  AddListParent('', Folder, Results);
  Results.Sort;
end;

function TAHMMediaFilesManager.BrowseMusic(var Folder: WideString; Results: TAHMMediaList): Boolean;
var
  i: Integer;
  Types: TAHMMediaTypes;
begin
  // Use functions above to retrieve media from relevant content folder
  Result := False;
  Types := [mtParent, mtFolder, mtAudio, mtVideo];

  // Use virtual path manager to parse requested folder
  FPath.Path := Folder;

  if ClearMediaList(Results) then
  begin
    // Check whether this is a filesystem or metadata folder
    if FPath.VirtualPath <> vmpFolders then
      ListMetaFolder(Results)
    else
      with ApplicationManagerInstance.MediaManager.MediaLocations do
      begin
        for i := 0 to Pred(MusicFolders.Count) do
          ListVirtualFolder(MusicFolders[i], 'Folders\', FPath.URL, Types, Results);
        Result := True;

        // Add default parent folder item to results & sort
        AddListParent('Folders\', FPath.URL, Results);
        Results.Sort;
      end;
  end;
end;

function TAHMMediaFilesManager.BrowseRadio(Folder: WideString; Results: TAHMMediaList): Boolean;
var
  i: Integer;
  Genre: String;
begin
  // Use function above to retrieve media from relevant content folder
  Result := False;
  if ClearMediaList(Results) then
  begin
    with ApplicationManagerInstance.MediaManager.Radio do
    begin
      // Check virtual path to build results
      if Folder = '' then
      begin
        // Root - display list of genres
        Results.AddItem('', FTxAllStations, mtFolder, '', '\', '', '');
        for i := 0 to Pred(Genres.Count) do
        begin
          Genre := Genres[i] + '\';
          Results.AddItem(Genre, Genre, mtFolder, '', Genre, '', '');
        end;
      end
      else if Folder = '\' then
      begin
        // All stations/genres. Don't show dummy station if we have real ones
        for i := Min(Pred(Stations.Count), 1) to Pred(Stations.Count) do
          with Stations[i] do
            Results.AddItem(Name, DisplayName, mtRadio, '', '', Filename, '');
      end
      else
      begin
        // Specific genre - filter stations
        for i := 0 to Pred(Stations.Count) do
        begin
          Genre := Stations[i].Genre + '\';
          if Folder = Genre then
            with Stations[i] do
              Results.AddItem(Name, DisplayName, mtRadio, '', '', Filename, '');
        end;
      end;
    end;

    Result := True;
  end;

  // Add default parent folder item to results & sort
  AddListParent('', Folder, Results);
  Results.Sort;
end;

function TAHMMediaFilesManager.BrowseMedia(Folder: WideString; Results: TAHMMediaList): Boolean;
var
  i: Integer;
begin
  // Use function above to retrieve media from relevant content folder
  Result := False;
  if ClearMediaList(Results) then
    with ApplicationManagerInstance.MediaManager.MediaLocations do
    begin
      for i := 0 to Pred(MusicFolders.Count) do
        ListVirtualFolder(MusicFolders[i], '', Folder, [mtAll], Results);
      for i := 0 to Pred(VideoFolders.Count) do
        ListVirtualFolder(VideoFolders[i], '', Folder, [mtAll], Results);
      for i := 0 to Pred(ImageFolders.Count) do
        ListVirtualFolder(ImageFolders[i], '', Folder, [mtAll], Results);
      Result := True;
    end;

  // Add default parent folder item to results & sort
  AddListParent('', Folder, Results);
  Results.Sort;
end;


end.

